package org.fhnw.aigs.server.communication; import java.io.*; import java.net.*; import java.util.*; import javax.swing.JOptionPane; import org.fhnw.aigs.server.common.LogRouter; import org.fhnw.aigs.server.common.LoggingLevel; import org.fhnw.aigs.server.common.ServerConfiguration; /** * This class is responsible for the outgoing connections. It connects the * server to the clients. Due to the fact that the class uses the Singleton * Pattern, it is not possible to instantiate ClientCommunication directly. In * order to get to an instance, use {@link ServerCommunication#getInstance} * instead.<br> * v1.0 Initial release<br> * v1.1 Functional changes<br> * v1.2 Changing of logging * @version 1.2 (Raphael Stoeckli, 24.02.2015) * @author Matthias Stöckli (v1.0) */ public class ServerCommunication implements Runnable { /** * The running state of the current instance */ private boolean runState; /** * Private constructor to prevent instantiation */ private ServerCommunication() { } /** * Gets the only instance of ClientCommunication. Synchronized indicates * that this Singleton is thread-safe. * * @return The ServerCommunication instance */ public static synchronized ServerCommunication getInstance() { return ServerCommunication.ServerCommunicationHolder.INSTANCE; } /** * Gets the running state of the current instance * @return runstate * @since v1.1 */ public boolean getRunState() { return runState; } /** * Provides a container for the ServerCommunication singleton */ private static final class ServerCommunicationHolder { private static final ServerCommunication INSTANCE = new ServerCommunication(); } /** * The server socket which allows the server to connect to clients */ private static ServerSocket serverSocket = null; /** * The client (sockets) which are registered. */ private static ArrayList<Socket> clientSockets = new ArrayList<Socket>(); @Override public void run() { runState = true; establishConnection(); } /** * Stops the server and closes all connections * @since v1.1 */ public void stop() { runState = false; // While-Loop will end cleanupThreads(true); // Stop all and clean up try { serverSocket.close(); } catch (IOException ex) { //LOG//Logger.getLogger(ServerMessageBroker.class.getName()).log(Level.INFO, "Could not close server socket", ex); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.waring, "Could not close server socket", ex); } } /** * Method to cleaning up the list of threads. Only closed clientSocket connections will be removed * @param stopAll Stopps all active clientSocket connections if true * @since v1.1 */ private static void cleanupThreads(boolean stopAll) { Iterator<Socket> i = clientSockets.iterator(); Socket s; while(i.hasNext()) { s = i.next(); if (stopAll == true) { try { s.close(); } catch(IOException ex) { //LOG//Logger.getLogger(ServerMessageBroker.class.getName()).log(Level.INFO, "Could not close client socket", ex); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.waring, "Could not close client socket", ex); } } if (s.isClosed() == true) { i.remove(); } } } /** * This method will open new client sockets. It will start new * ServerMessageBroker Threads for every new connection that has * successfully been established. This method will run endlessly. */ private void establishConnection() { cleanupThreads(true); // Stop all and clean up try { while (runState) { //Accept new client sockets Socket clientSocket = ServerCommunication.serverSocket.accept(); clientSockets.add(clientSocket); cleanupThreads(false); // Only clean up closed connections //LOG//Logger.getLogger(ServerMessageBroker.class.getName()).log(Level.INFO, "New connection established! Address: {0}", clientSocket.getInetAddress()); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.info, "New connection established! Address: {0}", clientSocket.getInetAddress()); // Create new Thread of ServerMessageBroker for new connection and start the thread Thread serverMessageBrokerThread = new Thread(new ServerMessageBroker(clientSocket)); serverMessageBrokerThread.setName("ServerMessageBrokerThread" + clientSocket.getInetAddress().toString()); serverMessageBrokerThread.start(); } } catch (IOException ex) { if (runState) // Error while running { //LOG//Logger.getLogger(ServerMessageBroker.class.getName()).log(Level.SEVERE, "Could not establish connection", ex); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.severe, "Could not establish connection", ex); } else // Connection closed { //LOG//Logger.getLogger(ServerMessageBroker.class.getName()).log(Level.INFO, "Connection was interrupted due to shutdown", ex); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.waring, "Connection was interrupted due to shutdown", ex); } } catch (Exception ex) // All other Exceptions { //LOG//Logger.getLogger(ServerMessageBroker.class.getName()).log(Level.SEVERE, "An unknown exception occurred while establishing connection", ex); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.severe, "An unknown exception occurred while establishing connection", ex); } } /** * Connects to the server using a {@link ServerSocket}. */ public static void setUpServerSocket() { boolean serverSetupSuccessful = false; int port = ServerConfiguration.getInstance().getPortNumber(); do { try { //LOG//Logger.getLogger(ServerCommunication.class.getName()).log(Level.INFO, "Try to connect to port {0}", port); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.info, "Try to connect to port {0}", port); serverSocket = new ServerSocket(port); // Tries to establish a connection on the standard port serverSetupSuccessful = true; continue; } catch (IOException ex) { //LOG//Logger.getLogger(ServerCommunication.class.getName()).log(Level.SEVERE, "Could not listen to port " + port + ", server will shut down.", ex); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.severe, "Could not listen to port " + port + ", server will shut down.", ex); JOptionPane.showMessageDialog(null, "Could not connect. Another instance of the AIGS seems to be running.", "There is a problem.", JOptionPane.ERROR_MESSAGE); System.exit(1); } catch (Exception ex) // All other exceptions { //LOG//Logger.getLogger(ServerCommunication.class.getName()).log(Level.SEVERE, "An unknown Error occurred. Server will shut down.", ex); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.severe, "An unknown Error occurred. Server will shut down.", ex); JOptionPane.showMessageDialog(null, "An unknown Error occurred", "There is a problem.", JOptionPane.ERROR_MESSAGE); System.exit(1); } // Allows the user to add corrections via the command line Scanner scanner = new Scanner(System.in); // Object for obtaining a console input. String input = ""; // Value of the console input // Regular expression to check for the correct format and port range (whole numbers from 0 to 65535) String portRegex = "^0*(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$"; // As long as the input does not match the port regex and the user did not type "exit", an input loop will run while (input.matches(portRegex) == false || input.equals("exit")) { input = scanner.next(); // Get the string input from console if (input.equals("exit")) { // Check for the keyword "exit" //LOG//Logger.getLogger(ServerCommunication.class.getName()).log(Level.INFO, "Shut down server..."); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.system, "Shut down server..."); System.exit(0); // ShutdownCleanUp will take care of the rest // If the input matches the port regex, the server tries to establish a connection under the new port number } else if (input.matches(portRegex)) { port = Integer.parseInt(input); break; } else { //LOG//Logger.getLogger(ServerCommunication.class.getName()).log(Level.INFO, "Invalid port number '{0}'. Please enter a number between 0 and 65535", port); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.waring, "Invalid port number '{0}'. Please enter a number between 0 and 65535", port); } } } while (serverSetupSuccessful == false); //LOG//Logger.getLogger(ServerCommunication.class.getName()).log(Level.INFO, "Connected successfully to port {0}, start listening...\nExternal IP: " + getExternalIp(), port); LogRouter.log(ServerMessageBroker.class.getName(), LoggingLevel.info, "Connected successfully to port {0}, start listening...\nExternal IP: " + getExternalIp(), port); } /** * A helper method to find out the external IP by using an external PHP * script. The script will reply the server's IP address in plain text.<br> * The script is as follows:<br> * <code><?php echo $_SERVER['REMOTE_ADDR']; ?></code> * * @return The server's external IP. */ public static String getExternalIp() { try { URL whatIsMyIPURL = new URL(ServerConfiguration.getInstance().getWhatIsMyIpUrl()); BufferedReader in = new BufferedReader(new InputStreamReader(whatIsMyIPURL.openStream(), "UTF-8")); return in.readLine(); } catch (Exception ex) { return "unknown"; } } }